package com.paolocorno.datatable;

import com.googlecode.xmlzen.XmlSlicer;
import com.googlecode.xmlzen.XmlSlicerList;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.ListIterator;
import javax.swing.table.AbstractTableModel;

/**
 *
 * @author Paolo Corno
 */
@DataTableAnnotation(xmlTag = "RECORDSET")
public abstract class AbstractDataTable extends AbstractTableModel {

    /**
     *
     */
    protected ArrayList<AbstractDataTableRecord> model = new ArrayList();
    /**
     *
     */
    protected Class tableRecordClass;

    /**
     *
     * @param tableRecordClass
     */
    public AbstractDataTable(Class tableRecordClass) {
        this.tableRecordClass = tableRecordClass;
    }

    /**
     *
     * @return
     */
    public ArrayList<AbstractDataTableRecord> getModel() {
        return model;
    }

    /**
     *
     * @param model
     */
    public void setModel(ArrayList<AbstractDataTableRecord> model) {
        this.model = model;
    }

    private boolean cellExists(int rowIndex, int columnIndex) {
        if (model == null) {
            return false;
        }
        if (model.size() <= rowIndex) {
            return false;
        }

        if (model.get(rowIndex).getRecordFieldsCount() < columnIndex) {
            return false;
        }
        return true;
    }

    @Override
    public int getRowCount() {
        return model.size();
    }

    @Override
    public int getColumnCount() {
        if (model.isEmpty()) {
            return 0;
        }
        return model.get(0).getRecordFieldsCount();
    }

    @Override
    public String getColumnName(int columnIndex) {
        if (!cellExists(0, columnIndex)) {
            return null;
        }
        return model.get(0).getFieldName(columnIndex);
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        if (!cellExists(0, columnIndex)) {
            return null;
        }
        Class clazz = model.get(0).getFieldType(columnIndex);
        return clazz;

    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        if (!cellExists(rowIndex, columnIndex)) {
            return false;
        }
        if (!model.get(rowIndex).isReadonly(columnIndex)) {
            return true;
        } else {
            return false;
        }
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        if (!cellExists(rowIndex, columnIndex)) {
            return null;
        }
        return model.get(rowIndex).getFieldValue(columnIndex);
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        if (!cellExists(rowIndex, columnIndex)) {
            return;
        }
        model.get(rowIndex).setFieldValue(columnIndex, aValue);
    }

    /**
     *
     * @param record
     */
    public void addRecord(AbstractDataTableRecord record) {
        model.add(record);
    }

    /**
     *
     * @param rowIndex
     */
    public void removeRecord(int rowIndex) {
        if (!cellExists(rowIndex, 0)) {
            return;
        }
        model.remove(rowIndex);
    }

    /**
     *
     * @param rowIndex
     * @return
     */
    public AbstractDataTableRecord getRecord(int rowIndex) {
        if (!cellExists(rowIndex, 0)) {
            return null;
        }
        return model.get(rowIndex);
    }

    /**
     *
     * @param columnIndex
     */
    public void sort(int columnIndex) {
        if (!model.isEmpty()) {
            Collections.sort(model, AbstractDataTableRecord.createRecordComparator(columnIndex));
        }
    }

    /**
     *
     * @param columnName
     * @return
     */
    public AbstractDataTable sort(String columnName) {
        AbstractDataTable result = this.clone();
        if (!result.getModel().isEmpty()) {
            Collections.sort(result.getModel(), AbstractDataTableRecord.createRecordComparator(columnName));
        }
        return result;
    }

    /**
     *
     * @param fieldName
     * @param token
     * @return
     */
    public AbstractDataTable search(String fieldName, Object token) {
        try {
            Constructor constructor = this.getClass().getConstructor(new Class[]{tableRecordClass.getClass()});
            AbstractDataTable result = (AbstractDataTable) (constructor.newInstance(new Object[]{tableRecordClass}));
            ArrayList<AbstractDataTableRecord> newModel = new ArrayList();
            for (int i = 0; i < model.size(); i++) {
                if (model.get(i).getFieldValue(fieldName).equals(token)) {
                    newModel.add(model.get(i));
                }
            }
            result.setModel(newModel);
            return result;
        } catch (Exception ex) {
            return null;
        }
    }

    @Override
    public AbstractDataTable clone() {
        try {
            Constructor constructor = this.getClass().getConstructor(new Class[]{tableRecordClass.getClass()});
            AbstractDataTable result = (AbstractDataTable) (constructor.newInstance(new Object[]{tableRecordClass}));
            ArrayList<AbstractDataTableRecord> newModel = new ArrayList();
            for (int i = 0; i < model.size(); i++) {
                newModel.add(model.get(i));
            }
            result.setModel(newModel);
            return result;
        } catch (Exception ex) {
            return null;
        }
    }

    /**
     *
     * @param describeFields
     * @return
     */
    public String toXml(boolean describeFields) {
        String ret = "";
        DataTableAnnotation da = (DataTableAnnotation) this.getClass().getAnnotation(DataTableAnnotation.class);

        if (model.size() > 0) {
            ret += "<" + da.xmlTag()
                    + " num=\"" + model.size() + "\">";
            for (int i = 0; i < model.size(); i++) {
                DataTableRecordAnnotation ra = model.get(i).getClass().getAnnotation(DataTableRecordAnnotation.class);
                ret += "<" + ra.xmlTag()
                        + " id=\"" + i + "\">";
                String strLine = model.get(i).toXml(describeFields);
                ret += strLine;
                ret += "</" + ra.xmlTag() + ">";
            }
            ret += "</" + da.xmlTag() + ">";
        }
        return ret;
    }

    /**
     *
     * @param file
     * @return
     */
    public boolean toXml(File file) {
        boolean ret = false;
        try {
            if (!file.delete()) {
                return false;
            }
            FileWriter fw = new FileWriter(file);
            fw.append(toXml(false));
            fw.flush();
            fw.close();
            ret = true;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return ret;
    }

    /**
     *
     * @param input
     * @return
     */
    public boolean fromXml(String input) {
        boolean ret = true;

        model.clear();

        try {
            Class clz = this.getClass();
            DataTableAnnotation da = (DataTableAnnotation) clz.getAnnotation(DataTableAnnotation.class);
            XmlSlicer slicer = XmlSlicer.cut(input).get(da.xmlTag());

            DataTableRecordAnnotation ra = (DataTableRecordAnnotation) tableRecordClass.getAnnotation(DataTableRecordAnnotation.class);
            XmlSlicerList recordset = slicer.getTags(ra.xmlTag());

            ListIterator ii = recordset.listIterator();
            while (ii.hasNext()) {
                XmlSlicer record = (XmlSlicer) ii.next();
                String strValue = record.value().toString();
                AbstractDataTableRecord rec;
                Constructor constructor = tableRecordClass.getConstructor(new Class[]{});
                rec = (AbstractDataTableRecord) (constructor.newInstance(new Object[]{}));

                ((AbstractDataTableRecord) rec).fromXml(strValue);
                model.add((AbstractDataTableRecord) rec);
            }
        } catch (Exception ex) {
            ret = false;
        }

        return ret;
    }

    /**
     *
     * @param file
     * @return
     */
    public boolean fromXml(File file) {
        boolean ret = false;
        try {
            StringBuilder fileData = new StringBuilder();
            FileInputStream fstream = new FileInputStream(file);
            DataInputStream in = new DataInputStream(fstream);
            BufferedReader br = new BufferedReader(new InputStreamReader(in));
            String strLine;
            while ((strLine = br.readLine()) != null) {
                fileData.append(strLine);
            }
            in.close();
            String strXml = fileData.toString();
            fromXml(strXml);
        } catch (java.io.IOException ex) {
            ex.printStackTrace();
        }
        return ret;
    }
}
